#include <system.h>
#include "kegscale_mk2.h"
#include "utils.h"
#include "eeprom_utils.h"

// Pic Pack includes
#include "config.h"
#include "pic_utils.h"
#include "pic_serial.h"
#include "pic_usb.h"
#include "usb_cdc_class.h"

// System includes
#include "string.h"
#include "stdlib.h"

// a check to see if the usb connection has been configured yet
bit io_ready = 0;

#ifndef SIMULATOR
    #define io_print_str usb_cdc_print_str
    #define io_putc      usb_cdc_putc
    #define io_rx_avail  usb_cdc_rx_avail
    #define io_getc      usb_cdc_getc
#else
    #define io_print_str serial_print_str
    #define io_putc      serial_putc
    #define io_rx_avail  serial_rx_avail
    #define io_getc      serial_getc
#endif

void io_print_int(uint32_t i) {
    char buffer[11];	// up to 5 characters plus \0
    uint8_t count = 10;
	buffer[10] = '\0';
	do {
		count--;
		buffer[count] = '0' + i % 10;
		i = i / 10;
	} while (i > 0);	
	while (buffer[count]) {
        io_putc(buffer[count]);
        count++;
    }  
}

// Test line for display
char serial_buffer[7]= '\0';

void usb_device_configured_callback() {
	io_ready = 1;
}	

void initialize_io() {
#ifndef SIMULATOR
    usb_cdc_setup();
    usb_setup();
    turn_usb_ints_on();

    delay_ms(100);
    usb_enable_module();
#else
    // set serial to run at 9600bps 
    serial_setup(SPBRG_9600);
    io_ready = 1;
#endif
}

//                0        1         2         3         4         5         6
//                123456789012345678901234567890123456789012345678901234567890
void show_help() {
    io_print_str((rom char *)"Kegscale Mk2 Version: ");
    io_print_str(version);
    io_print_str((rom char *)
                 "\r\nd<keg #>  : Display keg info for selected keg"
                 "\r\nw<width>  : Set LCD width"
                 "\r\nh<height> : Set LCD height"
                 "\r\nt<delay>  : Set LCD power-off delay"
                 "\r\n#<count>  : Set # of kegs"
                 "\r\nF<keg #>  : Set selected keg as full");
    io_print_str((rom char *)
                 "\r\ne<keg #>  : calibrate selected keg (empty)"
                 "\r\nf<keg #>  : calibrate selected keg (full)"
                 "\r\nz<keg #><value>   : Set Hz/Pint value"
                 "\r\nn<keg #><str>     : Set name for selected keg"
                 "\r\np<keg #><# pints> : Initialize selected keg max-pints");
}

void show_keginfo(uint8_t keg_num) {
    read_keginfo(keg_num);

    uint8_t val = eeprom_read(LCD_HEIGHT_ADDR);
    io_print_str((rom char *)"LCD size: ");
    io_print_int(val);
    io_putc('x');
    uint8_t lcd_width = eeprom_read(LCD_WIDTH_ADDR);
    io_print_int(lcd_width);
    io_putc('\r');
    io_putc('\n');

    val = eeprom_read(NUM_KEGS_ADDR);
    io_putc('1' + keg_num);
    io_print_str((rom char *)" of ");
    io_putc('0' + val);
    io_print_str((rom char *)" kegs: ");
    unsigned short ptr = KEG_NAME_ADDR + (lcd_width - 2) * keg_num;
    for (uint8_t i = 0; i < lcd_width; i++) {
        uint8_t c1 = eeprom_read(ptr++);
        if(c1 == 0)
            break;
        io_putc(c1);
    }

    io_print_str((rom char *)"\r\n\tEmpty:     ");
    io_print_int(keginfo.keg_empty);
    io_print_str((rom char *)"\r\n\tFull:      ");
    io_print_int(keginfo.keg_full);
    io_print_str((rom char *)"\r\n\tHz/pint:   ");
    io_print_int(keginfo.hz_per_pint);
    io_print_str((rom char *)"\r\n\tMax pints: ");
    io_print_int(keginfo.max_pints);
    io_print_str((rom char *)"\r\n\tCur Pints: ");
    io_print_int(num_pints[keg_num] / 128);

    io_print_str((rom char *)"\r\n\tCur Value: ");
    io_print_int(get_keg_level(keg_num));
}

void show_prompt() {
    serial_buffer[0] = '\0'; // clear the buffer
    io_putc('\r');      // print new line
    io_putc('\n');      // print new line
    io_putc('>');       // print the prompt
}

// Keep adding characters to our input buffer
// when we enter a command, then act on it
void handle_string() {
    uint8_t first_char = serial_buffer[0];
    uint8_t keg_num = serial_buffer[1] - '1';
    uint8_t len = strlen(serial_buffer);
    if (len < 2 || first_char == '?') {
        show_help();
        return;
    } else if (first_char == 'n') {
        uint8_t ptr, pos;
        if (serial_buffer[1] < ' ') {
            keg_num = serial_buffer[1] - 20;
            pos = serial_buffer[2];
            ptr = 3;
        } else {
            pos = 0;
            ptr = 2;
        }
        uint8_t lcd_width = eeprom_read(LCD_WIDTH_ADDR);
        eepaddr_t eep_ptr = KEG_NAME_ADDR + (lcd_width - 2) * keg_num + pos;
        while(serial_buffer[ptr] && pos < lcd_width - 3) {
            eeprom_uint8_write(eep_ptr++, serial_buffer[ptr]);
            ptr++;
            pos++;
        }
        if(pos < lcd_width - 3 && ptr == sizeof(serial_buffer)-1) {
            //User hasn't hit CR yet
            serial_buffer[1] = keg_num + 20;
            serial_buffer[2] = pos;
            serial_buffer[3] = '\0';
        } else {
            while(pos < lcd_width - 3) {
                eeprom_uint8_write(eep_ptr++, ' ');
                pos++;
            }
            show_prompt();
        }
        return;
    } else if (first_char == 't') {
        //timeout
    } else if (first_char == 'w') {
        //LCD width
        uint8_t width = atoi(serial_buffer + 1);
        eeprom_uint8_write(LCD_WIDTH_ADDR, width);
        return;
    } else if (first_char == 'h') {
        //LCD height
        uint8_t height = atoi(serial_buffer + 1);
        eeprom_uint8_write(LCD_HEIGHT_ADDR, height);
        return;
    }

    if(keg_num > 7) {
        io_print_str((rom char *)"\r\nKeg count must be between 1 and 8");
        return;
    }
    uint8_t offset = CALC_KEGINFO_OFFSET(keg_num);
    switch (first_char) {
        case '#':
            //Number of kegs
            eeprom_uint8_write(NUM_KEGS_ADDR, keg_num + 1);
            break;
        case 'd':
            show_keginfo(keg_num);
            break;
        case 'e':
        case 'f':
        case 'F':
            uint32_t keg_level = get_keg_level(keg_num);
            if (first_char == 'e') {
                keginfo.keg_empty = keg_level;
            } else {
                eeprom_uint24_write(offset + KEGINFO_KEG_FULL_OFFSET, keg_level);
                keginfo.keg_full = keg_level;
            }
            if (first_char != 'F') {
                keginfo.hz_per_pint = CALC_HZ_PER_PINT();
                eeprom_uint24_write(offset + KEGINFO_HZ_PER_PINT_OFFSET, keginfo.hz_per_pint);
            }
            break;
        case 'p':
            //Number of pints
            keginfo.max_pints = atoi(serial_buffer + 2);
            eeprom_uint8_write(offset + KEGINFO_MAX_PINTS_OFFSET, keginfo.max_pints);
            break;
        case 'z':
            //Set Hz/pint
            // Note this will only let you have a value between 0-9999
            // We may need to fix this in the future
            keginfo.hz_per_pint = atoi(serial_buffer + 2);
            eeprom_uint8_write(offset + KEGINFO_HZ_PER_PINT_OFFSET, keginfo.hz_per_pint);
            break;
    }
    return;
}

uint8_t handle_io() {
    if (io_ready == 0)
        return 0;
    if (! io_rx_avail())
        return 1;
    uint8_t rec = io_getc();         // get the character from the fifo
    io_putc(rec);                    // print it out so we can see what we typed
    if (rec == '\r') {               // did we press return?
        handle_string();
        show_prompt();
    } else {
        uint8_t len = strlen(serial_buffer); // add the character to our line input
        serial_buffer[len] = rec;
        serial_buffer[len+1] = '\0';
        if(len == sizeof(serial_buffer) - 2) {
            if (serial_buffer[0] == 'n') {
                handle_string();
            } else {
                io_print_str((rom char *)"\rString has exceeded max length\rResetting");
                show_prompt();
            }
        }
    }
    return 1;
}
